Completed
Pull Request — develop (#128)
by Xaver
01:22
created

labellayer.js ➔ ... ➔ L.GridLayer.extend.prepareLabels   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 94

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
c 1
b 0
f 0
nc 4
dl 0
loc 94
rs 8.4378
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
define(['leaflet', 'rbush', 'helper', 'moment'],
2
  function (L, rbush, helper, moment) {
3
    'use strict';
4
5
    var groupOnline;
6
    var groupOffline;
7
    var groupNew;
8
    var groupLost;
9
    var groupLines;
10
11
12
    var labelLocations = [['left', 'middle', 0 / 8],
13
      ['center', 'top', 6 / 8],
14
      ['right', 'middle', 4 / 8],
15
      ['left', 'top', 7 / 8],
16
      ['left', 'ideographic', 1 / 8],
17
      ['right', 'top', 5 / 8],
18
      ['center', 'ideographic', 2 / 8],
19
      ['right', 'ideographic', 3 / 8]];
20
    var labelShadow;
21
    var bodyStyle = { fontFamily: 'sans-serif' };
22
    var nodeRadius = 4;
23
24
    var cFont = document.createElement('canvas').getContext('2d');
25
26
    function measureText(font, text) {
27
      cFont.font = font;
28
      return cFont.measureText(text);
29
    }
30
31
    function mapRTree(d) {
32
      return { minX: d.position.lat, minY: d.position.lng, maxX: d.position.lat, maxY: d.position.lng, label: d };
33
    }
34
35
    function prepareLabel(fillStyle, fontSize, offset, stroke) {
36
      return function (d) {
37
        var font = fontSize + 'px ' + bodyStyle.fontFamily;
38
        return {
39
          position: L.latLng(d.nodeinfo.location.latitude, d.nodeinfo.location.longitude),
40
          label: d.nodeinfo.hostname,
41
          offset: offset,
42
          fillStyle: fillStyle,
43
          height: fontSize * 1.2,
44
          font: font,
45
          stroke: stroke,
46
          width: measureText(font, d.nodeinfo.hostname).width
47
        };
48
      };
49
    }
50
51
    function calcOffset(offset, loc) {
52
      return [offset * Math.cos(loc[2] * 2 * Math.PI),
53
    // offset * Math.sin(loc[2] * 2 * Math.PI)];
54
    }
55
56
    function labelRect(p, offset, anchor, label, minZoom, maxZoom, z) {
57
      var margin = 1 + 1.41 * (1 - (z - minZoom) / (maxZoom - minZoom));
58
59
      var width = label.width * margin;
60
      var height = label.height * margin;
61
62
      var dx = {
63
        left: 0,
64
        right: -width,
65
        center: -width / 2
66
      };
67
68
      var dy = {
69
        top: 0,
70
        ideographic: -height,
71
        middle: -height / 2
72
      };
73
74
      var x = p.x + offset[0] + dx[anchor[0]];
75
      var y = p.y + offset[1] + dy[anchor[1]];
76
77
      return { minX: x, minY: y, maxX: x + width, maxY: y + height };
78
    }
79
80
    function mkMarker(dict, iconFunc, router) {
81
      return function (d) {
82
        var m = L.circleMarker([d.nodeinfo.location.latitude, d.nodeinfo.location.longitude], iconFunc(d));
83
84
        m.resetStyle = function resetStyle() {
85
          m.setStyle(iconFunc(d));
86
        };
87
88
        m.on('click', function () {
89
          router.fullUrl({ node: d.nodeinfo.node_id });
90
        });
91
        m.bindTooltip(d.nodeinfo.hostname);
92
93
        dict[d.nodeinfo.node_id] = m;
94
95
        return m;
96
      };
97
    }
98
99
    function addLinksToMap(dict, linkScale, graph, router) {
100
      graph = graph.filter(function (d) {
101
        return 'distance' in d && !d.vpn;
102
      });
103
104
      return graph.map(function (d) {
105
        var opts = {
106
          color: linkScale(1 / d.tq),
107
          weight: 4,
108
          opacity: 0.5,
109
          dashArray: 'none'
110
        };
111
112
        var line = L.polyline(d.latlngs, opts);
113
114
        line.resetStyle = function resetStyle() {
115
          line.setStyle(opts);
116
        };
117
118
        line.bindTooltip(d.source.node.nodeinfo.hostname + ' – ' + d.target.node.nodeinfo.hostname + '<br><strong>' + helper.showDistance(d) + ' / ' + helper.showTq(d) + '</strong>');
119
        line.on('click', function () {
120
          router.fullUrl({ link: d.id });
121
        });
122
123
        dict[d.id] = line;
124
125
        return line;
126
      });
127
    }
128
129
    function getIcon(config, color) {
130
      Object.assign({}, config.icon.base, config.icon[color]);
131
    }
132
133
    return L.GridLayer.extend({
134
      onAdd: function (map) {
135
        L.GridLayer.prototype.onAdd.call(this, map);
136
        if (this.data) {
137
          this.prepareLabels();
138
        }
139
      },
140
      setData: function (data, map, nodeDict, linkDict, linkScale, router, config) {
141
        var iconOnline = getIcon(config, 'online');
142
        var iconOffline =getIcon(config, 'offline');
143
        var iconLost = getIcon(config, 'lost');
144
        var iconAlert = getIcon(config, 'alert');
145
        var iconNew = getIcon(config, 'new');
146
        // Check if init or data is already set
147
        if (groupLines) {
148
          groupOffline.clearLayers();
149
          groupOnline.clearLayers();
150
          groupNew.clearLayers();
151
          groupLost.clearLayers();
152
          groupLines.clearLayers();
153
        }
154
155
        var lines = addLinksToMap(linkDict, linkScale, data.graph.links, router);
156
        groupLines = L.featureGroup(lines).addTo(map);
157
158
        var nodesOnline = helper.subtract(data.nodes.all.filter(helper.online), data.nodes.new);
159
        var nodesOffline = helper.subtract(data.nodes.all.filter(helper.offline), data.nodes.lost);
160
161
        var markersOnline = nodesOnline.filter(helper.hasLocation)
162
          .map(mkMarker(nodeDict, function () {
163
            return iconOnline;
164
          }, router));
165
166
        var markersOffline = nodesOffline.filter(helper.hasLocation)
167
          .map(mkMarker(nodeDict, function () {
168
            return iconOffline;
169
          }, router));
170
171
        var markersNew = data.nodes.new.filter(helper.hasLocation)
172
          .map(mkMarker(nodeDict, function () {
173
            return iconNew;
174
          }, router));
175
176
        var markersLost = data.nodes.lost.filter(helper.hasLocation)
177
          .map(mkMarker(nodeDict, function (d) {
178
            if (d.lastseen.isAfter(moment(data.now).subtract(config.maxAgeAlert, 'days'))) {
179
              return iconAlert;
180
            }
181
182
            if (d.lastseen.isAfter(moment(data.now).subtract(config.maxAge, 'days'))) {
183
              return iconLost;
184
            }
185
            return null;
186
          }, router));
187
188
        groupOffline = L.featureGroup(markersOffline).addTo(map);
189
        groupLost = L.featureGroup(markersLost).addTo(map);
190
        groupOnline = L.featureGroup(markersOnline).addTo(map);
191
        groupNew = L.featureGroup(markersNew).addTo(map);
192
193
        this.data = {
194
          online: nodesOnline.filter(helper.hasLocation),
195
          offline: nodesOffline.filter(helper.hasLocation),
196
          new: data.nodes.new.filter(helper.hasLocation),
197
          lost: data.nodes.lost.filter(helper.hasLocation)
198
        };
199
        this.updateLayer();
200
      },
201
      updateLayer: function () {
202
        if (this._map) {
203
          this.prepareLabels();
204
        }
205
      },
206
      prepareLabels: function () {
207
        var d = this.data;
208
209
        // label:
210
        // - position (WGS84 coords)
211
        // - offset (2D vector in pixels)
212
        // - anchor (tuple, textAlignment, textBaseline)
213
        // - minZoom (inclusive)
214
        // - label (string)
215
        // - color (string)
216
217
        var labelsOnline = d.online.map(prepareLabel(null, 11, 8, true));
218
        var labelsOffline = d.offline.map(prepareLabel('rgba(212, 62, 42, 0.9)', 9, 5, false));
219
        var labelsNew = d.new.map(prepareLabel('rgba(48, 99, 20, 0.9)', 11, 8, true));
220
        var labelsLost = d.lost.map(prepareLabel('rgba(212, 62, 42, 0.9)', 11, 8, true));
221
222
        var labels = []
223
          .concat(labelsNew)
224
          .concat(labelsLost)
225
          .concat(labelsOnline)
226
          .concat(labelsOffline);
227
228
        var minZoom = this.options.minZoom;
229
        var maxZoom = this.options.maxZoom;
230
231
        var trees = [];
232
233
        var map = this._map;
234
235
        function nodeToRect(z) {
236
          return function (n) {
237
            var p = map.project(n.position, z);
238
            return { minX: p.x - nodeRadius, minY: p.y - nodeRadius, maxX: p.x + nodeRadius, maxY: p.y + nodeRadius };
239
          };
240
        }
241
242
        for (var z = minZoom; z <= maxZoom; z++) {
243
          trees[z] = rbush(9);
244
          trees[z].load(labels.map(nodeToRect(z)));
245
        }
246
247
        labels = labels.map(function (n) {
248
          var best = labelLocations.map(function (loc) {
249
            var offset = calcOffset(n.offset, loc);
250
            var i;
251
252
            for (i = maxZoom; i >= minZoom; i--) {
253
              var p = map.project(n.position, i);
254
              var rect = labelRect(p, offset, loc, n, minZoom, maxZoom, i);
255
              var candidates = trees[i].search(rect);
256
257
              if (candidates.length > 0) {
258
                break;
259
              }
260
            }
261
262
            return { loc: loc, z: i + 1 };
263
          }).filter(function (k) {
264
            return k.z <= maxZoom;
265
          }).sort(function (a, b) {
266
            return a.z - b.z;
267
          })[0];
268
269
          if (best !== undefined) {
270
            n.offset = calcOffset(n.offset, best.loc);
271
            n.minZoom = best.z;
272
            n.anchor = best.loc;
273
274
            for (var i = maxZoom; i >= best.z; i--) {
275
              var p = map.project(n.position, i);
276
              var rect = labelRect(p, n.offset, best.loc, n, minZoom, maxZoom, i);
277
              trees[i].insert(rect);
278
            }
279
280
            return n;
281
          }
282
          return undefined;
283
        }).filter(function (n) {
284
          return n !== undefined;
285
        });
286
287
        this.margin = 16;
288
289
        if (labels.length > 0) {
290
          this.margin += labels.map(function (n) {
291
            return n.width;
292
          }).sort().reverse()[0];
293
        }
294
295
        this.labels = rbush(9);
296
        this.labels.load(labels.map(mapRTree));
297
298
        this.redraw();
299
      },
300
      createTile: function (tilePoint) {
301
        var tile = L.DomUtil.create('canvas', 'leaflet-tile');
302
303
        var tileSize = this.options.tileSize;
304
        tile.width = tileSize;
305
        tile.height = tileSize;
306
307
        if (!this.labels) {
308
          return tile;
309
        }
310
311
        var s = tilePoint.multiplyBy(tileSize);
312
        var map = this._map;
313
        bodyStyle = window.getComputedStyle(document.querySelector('body'));
314
        labelShadow = bodyStyle.backgroundColor.replace(/rgb/i, 'rgba').replace(/\)/i, ',0.7)');
315
316
        function projectNodes(d) {
317
          var p = map.project(d.label.position);
318
319
          p.x -= s.x;
320
          p.y -= s.y;
321
322
          return { p: p, label: d.label };
323
        }
324
325
        var bbox = helper.getTileBBox(s, map, tileSize, this.margin);
326
        var labels = this.labels.search(bbox).map(projectNodes);
327
        var ctx = tile.getContext('2d');
328
329
        ctx.lineWidth = 5;
330
        ctx.strokeStyle = labelShadow;
331
        ctx.miterLimit = 2;
332
333
        function drawLabel(d) {
334
          ctx.font = d.label.font;
335
          ctx.textAlign = d.label.anchor[0];
336
          ctx.textBaseline = d.label.anchor[1];
337
          ctx.fillStyle = d.label.fillStyle === null ? bodyStyle.color : d.label.fillStyle;
338
339
          if (d.label.stroke) {
340
            ctx.strokeText(d.label.label, d.p.x + d.label.offset[0], d.p.y + d.label.offset[1]);
341
          }
342
343
          ctx.fillText(d.label.label, d.p.x + d.label.offset[0], d.p.y + d.label.offset[1]);
344
        }
345
346
        labels.filter(function (d) {
347
          return tilePoint.z >= d.label.minZoom;
348
        }).forEach(drawLabel);
349
350
        return tile;
351
      }
352
    });
353
  });
354